#include "OiPCH.hpp"
#include <QtCharts/QDateTimeAxis>

using namespace QtCharts;

namespace Oi
{
    namespace PLScannerCore
    {
        OiCheckedLegend::OiCheckedLegend(QWidget* parent)
            : QCheckBox(parent)
        {

        }

        void OiCheckedLegend::setColor(QColor bg)
        {
            QString style = QString("QCheckBox{color:%2}"
                "QCheckBox::indicator:checked{background-color:#%1}").arg(bg.rgb(), 6, 16, QChar('0'))
                .arg(PLScannerStyle::ChartCheckboxColor().rgb(), 6, 16, QChar('0'));
            this->setStyleSheet(style);
        }

        class DynamicSplineChartImpl : public Impl<DynamicSplineChart>
        {

        public:
            QMap<QString, QLineSeries*> _splines;
            QMap<QString, QScatterSeries*> _scatters;
            QMap<QString, OiCheckedLegend*> _checkboxs;
            QTimer _flashTimer;
            bool _isRunning;

        public:
            double _lower;
            double _upper;

        public:
            double _yMinRangeLower;
            double _yMinRangeUpper;

        public:
            int _xRange;

        public:
            DynamicSplineChart::FitnessMode _flashMode = DynamicSplineChart::FitToLimite;

        public:
            const QList<QColor> _colors = {"#5096ff", "#c24de8", "#54b21b", "#e84d4d", "#ebe771", "#ebe771", "#ebe771", "#ebe771"};
        };

        DynamicSplineChart::DynamicSplineChart(QStringList splines, QGraphicsItem *parent /*= Q_NULLPTR*/, Qt::WindowFlags wFlags /*= Qt::WindowFlags()*/)
            : QChart(parent, wFlags)
        {
            OI_I(DynamicSplineChart);

            this->setTheme((ChartTheme)PLScannerStyle::ChartDefaultTheme());
            this->setBackgroundVisible(false);
            this->setPlotAreaBackgroundBrush(PLScannerStyle::ChartBgColor());
            this->setPlotAreaBackgroundVisible(true);
            this->layout()->setContentsMargins(0, 0, 0, 0);
            this->setBackgroundRoundness(0);
            this->legend()->setVisible(false);
            //this->legend()->setAlignment(Qt::AlignTop);

            QDateTimeAxis *axisX = new QDateTimeAxis;
            axisX->setTickCount(13);
            axisX->setFormat("MM/dd hh:mm");
            axisX->setGridLinePen(QColor::fromRgb(50, 50, 50));
            axisX->setRange(QDateTime::currentDateTime().addSecs(-60 * 60), QDateTime::currentDateTime());
            this->addAxis(axisX, Qt::AlignBottom);

            QValueAxis *axisY = new QValueAxis;
            axisY->setTickCount(11);
            axisY->setRange(0.0, 10.0);
            axisY->setLabelFormat("%.1f");
            axisY->setGridLinePen(QColor::fromRgb(50, 50, 50));
            this->addAxis(axisY, Qt::AlignLeft);

            for (auto s : splines)
            {
                d->_splines[s] = nullptr;
                d->_scatters[s] = nullptr;
            }

            for (auto s : splines)
            {
                d->_checkboxs[s] = new OiCheckedLegend();
                d->_checkboxs[s]->setText(s);
                d->_checkboxs[s]->setColor(d->_colors[d->_splines.keys().indexOf(s)]);
                d->_checkboxs[s]->setChecked(true);
            }

            d->_isRunning = false;

            d->_flashTimer.setInterval(10000);

            d->_lower = 3.0;
            d->_upper = 6.0;

            d->_yMinRangeLower = -10000.0;
            d->_yMinRangeUpper = -10000.0;

            d->_xRange = 60 * 60;

            connect(&d->_flashTimer, &QTimer::timeout, this, &DynamicSplineChart::onFlash);
        }

        DynamicSplineChart::~DynamicSplineChart()
        {
            OI_E(DynamicSplineChart);
        }

        QMap<QString, OiCheckedLegend*> DynamicSplineChart::legends() const
        {
            return OI_IMPL()->_checkboxs;
        }

        QStringList DynamicSplineChart::splines() const
        {
            return OI_IMPL()->_splines.keys();
        }

        bool DynamicSplineChart::isRunning()
        {
            return OI_IMPL()->_isRunning;
        }

        void DynamicSplineChart::run(bool flag)
        {
            OI_F(DynamicSplineChart);

            d->_isRunning = flag;

            if (flag)
            {
                d->_flashTimer.start();
                onFlash();
            }
            else
            {
                d->_flashTimer.stop();
            }
        }

        void DynamicSplineChart::addValue(QString s, qreal dt, qreal value)
        {
            OI_F(DynamicSplineChart);

            if (!d->_isRunning)
            {
                return;
            }

            if (!d->_splines.contains(s))
            {
                return;
            }

            auto& ss = d->_splines[s];
            auto index = d->_splines.keys().indexOf(s);

            if (!ss)
            {
                ss = new QLineSeries();

                ss->setName(s);

                ss->setPen(QPen(d->_colors[index], 2));

                ss->append(dt, value);

                addSeries(ss);

                ss->attachAxis(this->axisX());

                ss->attachAxis(this->axisY());
            }
            else
            {
                if (ss->count() > 2000)
                {
                    ss->removePoints(0, 1000);
                }
                ss->append(dt, value);
            }

            auto& sc = d->_scatters[s];

            if(!sc)
            {
                sc = new QScatterSeries();

                sc->setBrush(d->_colors[index]);

                sc->setMarkerShape(QScatterSeries::MarkerShapeCircle);

                sc->setMarkerSize(5.0);

                sc->append(dt, value);

                addSeries(sc);

                sc->attachAxis(this->axisX());

                sc->attachAxis(this->axisY());
            }
            else
            {
                if(sc->count() > 2000)
                {
                    sc->removePoints(0, 1000);
                }
                sc->append(dt, value);
            }
        }

        void DynamicSplineChart::addValues(qreal dt, QMap<QString, qreal> values)
        {
            OI_F(DynamicSplineChart);

            if(!d->_isRunning)
            {
                return;
            }

            for (auto& key : values.keys())
            {
                addValue(key, dt, values[key]);
            }
        }

        void DynamicSplineChart::setValue(QString s, const QList<QPointF>& values)
        {
            OI_F(DynamicSplineChart);

            if(!d->_splines.contains(s))
            {
                return;
            }

            auto& ss = d->_splines[s];
            auto index = d->_splines.keys().indexOf(s);

            if(!ss)
            {
                ss = new QLineSeries();

                ss->setName(s);

                ss->setPen(QPen(d->_colors[index], 2));

                ss->append(values);

                addSeries(ss);

                ss->attachAxis(this->axisX());

                ss->attachAxis(this->axisY());
            }
            else
            {
                ss->replace(values);
            }

            auto& sc = d->_scatters[s];

            if(!sc)
            {
                sc = new QScatterSeries();

                sc->setBrush(d->_colors[index]);

                sc->setMarkerShape(QScatterSeries::MarkerShapeCircle);

                sc->setMarkerSize(5.0);

                sc->append(values);

                addSeries(sc);

                sc->attachAxis(this->axisX());

                sc->attachAxis(this->axisY());
            }
            else
            {
                sc->replace(values);
            }
        }

        void DynamicSplineChart::setValues(const QMap<QString, QList<QPointF>>& values)
        {
            OI_F(DynamicSplineChart);

            for(auto& key : values.keys())
            {
                setValue(key, values[key]);
            }
        }

        void DynamicSplineChart::setLimit(const double upper, const double lower)
        {
            OI_F(DynamicSplineChart);

            if (upper < lower)
            {
                d->_upper = lower;
                d->_lower = upper;
            }
            else
            {
                d->_upper = upper;
                d->_lower = lower;
            }

            //updatePlotAreaBackground();
        }

        void DynamicSplineChart::onFlash()
        {
            OI_F(DynamicSplineChart);

            this->axisX()->setRange(QDateTime::currentDateTime().addSecs(-d->_xRange), QDateTime::currentDateTime());

            yAxisFittness(d->_flashMode);

            emit flash();
        }

        void DynamicSplineChart::updatePlotAreaBackground()
        {
            OI_F(DynamicSplineChart);

            auto minY = ( (QValueAxis*)axisY() )->min();
            auto maxY = ( (QValueAxis*)axisY() )->max();

            QLinearGradient plotAreaGradient;

            plotAreaGradient.setStart(QPointF(0, plotArea().top()));
            plotAreaGradient.setFinalStop(QPointF(0, plotArea().bottom()));

            if (d->_upper < minY || d->_lower > maxY)
            {
                plotAreaGradient.setColorAt(0.0, QRgb(0xff1414));
                plotAreaGradient.setColorAt(1.0, QRgb(0xff1414));
            }
            else if(d->_upper > maxY && d->_lower < minY)
            {
                plotAreaGradient.setColorAt(0.0, QRgb(0x202020));
                plotAreaGradient.setColorAt(1.0, QRgb(0x202020));
            }
            else if (d->_upper > maxY)
            {
                plotAreaGradient.setColorAt(0.0, QRgb(0x202020));
                plotAreaGradient.setColorAt((maxY - d->_lower) / ( maxY - minY ), QRgb(0x202020));
                plotAreaGradient.setColorAt((maxY - d->_lower) / ( maxY - minY ) + 0.01, QRgb(0xff1414));
                plotAreaGradient.setColorAt(1.0,  QRgb(0xff1414));
            }
            else if (d->_lower < minY)
            {
                plotAreaGradient.setColorAt(0.0,  QRgb(0xff1414));
                plotAreaGradient.setColorAt((maxY -  d->_upper) / ( maxY - minY ) - 0.01, QRgb(0xff1414));
                plotAreaGradient.setColorAt((maxY - d->_upper ) / ( maxY - minY ), QRgb(0x202020));
                plotAreaGradient.setColorAt(1.0,  QRgb(0x202020));
            }
            else
            {
                plotAreaGradient.setColorAt(0.0,  QRgb(0xff1414));

                auto p1 = ( maxY - d->_upper ) / ( maxY - minY );
                auto p2 = ( maxY - d->_lower ) / ( maxY - minY );
                plotAreaGradient.setColorAt(( maxY - d->_upper ) / ( maxY - minY ) - 0.01, QRgb(0xff1414));
                plotAreaGradient.setColorAt(( maxY - d->_upper ) / ( maxY - minY ), QRgb(0x202020));

                plotAreaGradient.setColorAt(( maxY - d->_lower ) / ( maxY - minY ), QRgb(0x202020));
                plotAreaGradient.setColorAt(( maxY - d->_lower ) / ( maxY - minY ) + 0.01, QRgb(0xff1414));
                plotAreaGradient.setColorAt(1.0,  QRgb(0xff1414));
            }

            setPlotAreaBackgroundBrush(plotAreaGradient);
        }

        void DynamicSplineChart::setFlashFitMode(FitnessMode mode /*= FitToLimite*/)
        {
            OI_IMPL()->_flashMode = mode;
        }

        void DynamicSplineChart::yAxisFittness(FitnessMode mode)
        {
            OI_F(DynamicSplineChart);

            qreal maxY = -10000.0;
            qreal minY = +10000.0;

            if (mode == FitToData)
            {
                double percentage = 0.7;

                for(auto s : series())
                {
                    auto ps = ( (QXYSeries*)s )->points();
                    for(auto& p : ps)
                    {
                        if(p.y() > maxY)
                        {
                            maxY = p.y();
                        }
                        if(p.y() < minY)
                        {
                            minY = p.y();
                        }
                    }
                }

                if(qFuzzyCompare(maxY, -10000.0) || qFuzzyCompare(minY, +10000.0))
                {
                    this->axisY()->setRange(0.0, 10.0);
                    minY = 0.0;
                    maxY = 10.0;
                }
                else
                {
                    double minV = minY, maxV = maxY;
                    if(!qFuzzyCompare(d->_yMinRangeLower, -10000.0) && minV > d->_yMinRangeLower)
                    {
                        minV = d->_yMinRangeLower;
                    }
                    if(!qFuzzyCompare(d->_yMinRangeUpper, -10000.0) && maxV < d->_yMinRangeUpper)
                    {
                        maxV = d->_yMinRangeUpper;
                    }

                    minY = minV - ( 0.5 / percentage - 0.5 ) * ( maxV - minV );
                    maxY = maxV + ( 0.5 / percentage - 0.5 ) * ( maxV - minV );
                }
            }

            else if (mode == FitToLimite)
            {
                double percentage = 0.5;

                minY = d->_lower - ( 0.5 / percentage - 0.5 ) * ( d->_upper - d->_lower );
                maxY = d->_upper + ( 0.5 / percentage - 0.5 ) * ( d->_upper - d->_lower  );
            }

            if (fabs(minY - maxY) <= 1.0)
            {
                minY = minY - 0.1;
                maxY = maxY + 0.1;
            }

            this->axisY()->setRange(minY, maxY);
            
            //updatePlotAreaBackground();
        }

        void DynamicSplineChart::setMinYRange(const double upper, const double lower)
        {
            OI_F(DynamicSplineChart);
            d->_yMinRangeUpper = upper;
            d->_yMinRangeLower = lower;
        }

        void DynamicSplineChart::setYFormat(QString f)
        {
            OI_F(DynamicSplineChart);

            qobject_cast<QValueAxis*>( this->axisY() )->setLabelFormat(f);
        }

        void DynamicSplineChart::setXRange(int range)
        {
            OI_F(DynamicSplineChart);
            d->_xRange = range;
        }

        int DynamicSplineChart::xRange() const
        {
            return OI_IMPL()->_xRange;
        }

        void DynamicSplineChart::onLegendChecked()
        {
            OI_F(DynamicSplineChart);
            for (auto name : d->_checkboxs.keys())
            {
                if (d->_splines[name])
                {
                    d->_splines[name]->setVisible(d->_checkboxs[name]->isChecked());
                }
                if (d->_scatters[name])
                {
                    d->_scatters[name]->setVisible(d->_checkboxs[name]->isChecked());
                }
            }
        }

    }
}